package com.xxg.renrensearch.es;

import com.alibaba.fastjson.JSON;
import com.xxg.renrensearch.dao.Product;
import com.xxg.renrensearch.dao.RenrenShopShop;
import com.xxg.renrensearch.repository.RenrenShopShopRepository;
import com.xxg.renrensearch.util.ConvertUtil;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.metrics.ParsedCardinality;
import org.elasticsearch.search.aggregations.metrics.ParsedMax;
import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 索引库高级查询接口
 *
 * @author xuxiaogang
 * @date 2021-11-22
 */
@Repository
public class RenRenShopEs {
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    private final RenrenShopShopRepository renrenShopShopRepository;

    public RenRenShopEs(RenrenShopShopRepository renrenShopShopRepository) {
        this.renrenShopShopRepository = renrenShopShopRepository;
    }

    /**
     * 查询特定索引库中的索引最大ID和总记录数
     *
     * @return 结果
     */
    public <T> Integer[] getMaxIdCount(Class<T> clazz, String field) {
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(Pageable.ofSize(1))
                .withAggregations(AggregationBuilders.max("max_id").field(field),
                        AggregationBuilders.count("id_count").field(field)).build();
        SearchHits<T> searchHits = elasticsearchRestTemplate.search(searchQuery, clazz);
        Integer[] integers = {0, 0};
        //获取聚合结果
        if (searchHits.getAggregations() != null) {
            Aggregations a = (Aggregations) searchHits.getAggregations().aggregations();
            ParsedMax b = (ParsedMax) a.getAsMap().get("max_id");
            ParsedValueCount count = (ParsedValueCount) a.getAsMap().get("id_count");
            integers[0] = (int) count.getValue();
            if (integers[0] > 0) {
                integers[1] = (int) b.getValue();
            } else {
                integers[1] = 0;
            }
        }
        return integers;
    }

    /**
     * 根据ID列表和索引库类删除索引数据
     *
     * @param clazz 类
     * @param field 字段
     * @param ids   ID列表
     * @param <T>   范型
     */
    public <T> void deleteAllByIds(Class<T> clazz, String field, List<Integer> ids) {
        TermsQueryBuilder query = new TermsQueryBuilder(field, ids);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(query).build();
        elasticsearchRestTemplate.delete(searchQuery, clazz);
    }

    /**
     * 更新店铺索引文档
     *
     * @param shops 更新的数据
     */
    public void updateShopIndex(List<RenrenShopShop> shops) {
        List<UpdateQuery> updateQueries = new ArrayList<>(shops.size());
        shops.forEach(e -> {
            Document document = Document.create();
            document.put("id", e.getId());
            document.put("name", e.getName());
            document.put("logo", e.getLogo());
            document.put("userId", e.getUserId());
            document.put("cityText", e.getCityText());
            document.put("cityCode", e.getCityCode());
            UpdateQuery updateQuery = UpdateQuery.builder(e.getId().toString()).withDocument(document)
                    .withDocAsUpsert(true).build();
            updateQueries.add(updateQuery);
        });
        elasticsearchRestTemplate.bulkUpdate(updateQueries, RenrenShopShop.class);
    }

    /**
     * 更新商品索引文档
     *
     * @param goods 更新的数据
     */
    public void updateGoodsIndex(List<Product> goods) {
        List<UpdateQuery> updateQueries = new ArrayList<>(goods.size());
        goods.forEach(e -> {
            Document document = Document.create().fromJson(JSON.toJSONString(e));
            UpdateQuery updateQuery = UpdateQuery.builder(e.getId()).withDocument(document)
                    .withDocAsUpsert(true).build();
            updateQueries.add(updateQuery);
        });
        elasticsearchRestTemplate.bulkUpdate(updateQueries, Product.class);
    }

    /**
     * 查询店铺的商品(按照spu分组并取根据制定字段排序)
     *
     * @param shopId         店铺ID
     * @param pageable       分页信息
     * @param requiredFields 返回的字段
     * @param orderField     排序字段
     * @param sortOrder      排序顺序
     * @param categories     商品分类
     * @return 结果
     */
    public List<Product> findByShopId(Integer shopId, Pageable pageable, String[] requiredFields, String orderField,
                                      SortOrder sortOrder, List<Integer> categories) {
        return findByShopId(shopId, pageable, requiredFields, orderField, sortOrder, categories, new ArrayList<>(0));
    }

    /**
     * 查询店铺的商品(按照spu分组并取根据制定字段排序)
     *
     * @param shopId         店铺ID
     * @param pageable       分页信息
     * @param requiredFields 返回的字段
     * @param orderField     排序字段
     * @param sortOrder      排序顺序
     * @param categories     商品分类
     * @param skuIds         需要过滤的商品spuId列表
     * @return 结果
     */
    public List<Product> findByShopId(Integer shopId, Pageable pageable, String[] requiredFields, String orderField,
                                      SortOrder sortOrder, List<Integer> categories, List<Integer> skuIds) {
        TermQueryBuilder query = new TermQueryBuilder("shopId", shopId);
        BoolQueryBuilder booleanQueryBuilder = new BoolQueryBuilder();
        booleanQueryBuilder.must(query);
        // 需要过滤或者排除的spuId列表
        if (skuIds.size() > 1) {
            Integer isInclude = skuIds.remove(0);
            TermsQueryBuilder termsQueryBuilder = new TermsQueryBuilder("spuId", skuIds);
            if (isInclude == 1) {
                // 过滤
                booleanQueryBuilder.must(termsQueryBuilder);
            } else {
                // 排除
                booleanQueryBuilder.mustNot(termsQueryBuilder);
            }
        }
        if (categories != null && categories.size() > 0) {
            TermsQueryBuilder categoriesQuery = new TermsQueryBuilder("categories", categories);
            booleanQueryBuilder.must(categoriesQuery);
        }
        FetchSourceFilter sourceFilter = new FetchSourceFilter(requiredFields, new String[]{});
        FieldSortBuilder sortBuilder = new FieldSortBuilder(orderField).order(sortOrder);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(pageable)
                .withSourceFilter(sourceFilter)
                .withQuery(booleanQueryBuilder)
                .withSorts(sortBuilder)
                .withCollapseField("spuId").build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        List<Product> products = new ArrayList<>();
        searchHits.getSearchHits().forEach(e -> products.add(e.getContent()));
        return products;
    }

    /**
     * 查询店铺的商品(按照spu分组)
     *
     * @param shopId         店铺ID
     * @param pageable       分页信息
     * @param requiredFields 返回字段
     * @param keyword        关键词
     * @param categories     商品分类
     * @return 结果
     */
    public List<Product> findByKeyword(Integer shopId, Pageable pageable, String[] requiredFields, String keyword,
                                       List<Integer> categories) {
        BoolQueryBuilder booleanQueryBuilder = new BoolQueryBuilder();
        if (shopId != null) {
            TermQueryBuilder query = new TermQueryBuilder("shopId", shopId);
            booleanQueryBuilder.must(query);
        }
        if (keyword != null) {
            MatchQueryBuilder query = new MatchQueryBuilder("skuName", keyword);
            booleanQueryBuilder.must(query);
        }
        if (categories != null && categories.size() > 0) {
            TermsQueryBuilder categoriesQuery = new TermsQueryBuilder("categories", categories);
            booleanQueryBuilder.must(categoriesQuery);
        }
        FetchSourceFilter sourceFilter = new FetchSourceFilter(requiredFields, new String[]{});
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(pageable)
                .withSourceFilter(sourceFilter)
                .withQuery(booleanQueryBuilder)
                .withCollapseField("spuId").build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        List<Product> products = new ArrayList<>();
        searchHits.getSearchHits().forEach(e -> products.add(e.getContent()));
        return products;
    }

    /**
     * 在指定商品范围内模糊查询商品
     *
     * @param spuIds         商品spuId范围
     * @param pageable       分页
     * @param requiredFields 返回字段
     * @param keyword        关键词
     * @return 结果
     */
    public List<Product> findByKeyword(List<Integer> spuIds, Pageable pageable, String[] requiredFields, String keyword) {
        BoolQueryBuilder booleanQueryBuilder = new BoolQueryBuilder();
        if (spuIds != null && spuIds.size() > 0) {
            TermsQueryBuilder query = new TermsQueryBuilder("spuId", spuIds);
            booleanQueryBuilder.must(query);
        }
        if (keyword != null) {
            MatchQueryBuilder query = new MatchQueryBuilder("skuName", keyword);
            booleanQueryBuilder.must(query);
        }
        FetchSourceFilter sourceFilter = new FetchSourceFilter(requiredFields, new String[]{});
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(pageable)
                .withSourceFilter(sourceFilter)
                .withQuery(booleanQueryBuilder)
                .withCollapseField("spuId").build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        List<Product> products = new ArrayList<>();
        searchHits.getSearchHits().forEach(e -> products.add(e.getContent()));
        return products;
    }

    /**
     * 根据SPUID列表查询SKU，根据SPUID去重，返回价格最小的SKU
     *
     * @param spuIds         ID列表
     * @param requiredFields 返回字段
     * @return 结果
     */
    public List<Product> findCollapseSkusBySpuIds(List<Integer> spuIds, String[] requiredFields) {
        if (spuIds == null || spuIds.size() == 0) {
            return new ArrayList<>(0);
        }
        String field = "spuId";
        FieldSortBuilder sortBuilder = new FieldSortBuilder("skuPrice").order(SortOrder.ASC);
        TermsQueryBuilder termsQueryBuilder = new TermsQueryBuilder(field, spuIds);
        NativeSearchQuery searchQuery;
        if (requiredFields != null) {
            FetchSourceFilter sourceFilter = new FetchSourceFilter(requiredFields, new String[]{});
            searchQuery = new NativeSearchQueryBuilder()
                    .withPageable(Pageable.ofSize(spuIds.size()))
                    .withSourceFilter(sourceFilter)
                    .withQuery(termsQueryBuilder)
                    .withSorts(sortBuilder)
                    .withCollapseField(field).build();
        } else {
            searchQuery = new NativeSearchQueryBuilder()
                    .withPageable(Pageable.ofSize(spuIds.size()))
                    .withQuery(termsQueryBuilder)
                    .withSorts(sortBuilder)
                    .withCollapseField(field).build();
        }
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        List<Product> products = new ArrayList<>();
        searchHits.getSearchHits().forEach(e -> products.add(e.getContent()));
        return products;
    }

    /**
     * 获取所有店铺的spu数量
     *
     * @return 结果
     */
    public Map<Integer, Integer> getShopSpuCount() {
        int size = (int) renrenShopShopRepository.count();
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(Pageable.ofSize(1))
                .withAggregations(AggregationBuilders.terms("shop_group").field("shopId").size(size)
                        .subAggregation(AggregationBuilders.cardinality("spuCount").field("spuId"))).build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        if (searchHits.getAggregations() != null) {
            Aggregations a = (Aggregations) searchHits.getAggregations().aggregations();
            ParsedTerms shopGroup = (ParsedTerms) a.getAsMap().get("shop_group");
            List<ParsedLongTerms.ParsedBucket> buckets = ConvertUtil.castList(shopGroup.getBuckets(),
                    ParsedLongTerms.ParsedBucket.class);
            Map<Integer, Integer> map = new HashMap<>(buckets.size() * 4 / 3 + 1);
            buckets.forEach(e -> {
                ParsedCardinality cardinality = (ParsedCardinality) e.getAggregations().getAsMap().get("spuCount");
                map.put(e.getKeyAsNumber().intValue(), (int) cardinality.getValue());
            });
            return map;
        }
        return new HashMap<>(0);
    }

    /**
     * 根据店铺查询SPU数量
     *
     * @param shopId 店铺ID
     * @return 结果
     */
    public Integer getShopSpuCount(Integer shopId) {
        TermQueryBuilder termQueryBuilder = new TermQueryBuilder("shopId", shopId);
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(Pageable.ofSize(1))
                .withQuery(termQueryBuilder)
                .withAggregations(AggregationBuilders.cardinality("spuCount").field("spuId")).build();
        SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
        if (searchHits.getAggregations() != null) {
            Aggregations a = (Aggregations) searchHits.getAggregations().aggregations();
            ParsedCardinality spuCount = (ParsedCardinality) a.getAsMap().get("spuCount");
            return (int) spuCount.getValue();
        }
        return 0;
    }

    /**
     * 批量查询商品信息
     *
     * @param maps 查询条件列表
     * @return 结果
     */
    public List<Product> multiQueryProduct(List<Map<String, String>> maps) {
        List<Query> queries = new ArrayList<>(maps.size());
        FieldSortBuilder sortBuilder = new FieldSortBuilder("skuPrice").order(SortOrder.ASC);
        String[] requiredFields1 = {"id", "shopId", "skuName", "spuThumb", "skuPrice"};
        String[] requiredFields2 = {"id", "shopId", "skuName", "spuThumb", "skuThumb", "skuPrice"};
        for (Map<String, String> map : maps) {
            String spuId = map.get("spuid");
            String skuId = map.get("skuid");
            if (spuId == null) {
                continue;
            }
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
                    .withPageable(Pageable.ofSize(1))
                    .withSorts(sortBuilder);
            TermQueryBuilder termQueryBuilder = new TermQueryBuilder("spuId", spuId);
            FetchSourceFilter sourceFilter;
            if (skuId != null) {
                sourceFilter = new FetchSourceFilter(requiredFields2, new String[]{});
                BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
                boolQueryBuilder.must(termQueryBuilder);
                TermQueryBuilder termQueryBuilder1 = new TermQueryBuilder("skuId", skuId);
                boolQueryBuilder.must(termQueryBuilder1);
                queryBuilder.withQuery(boolQueryBuilder);
            } else {
                sourceFilter = new FetchSourceFilter(requiredFields1, new String[]{});
                queryBuilder.withQuery(termQueryBuilder);
            }
            queryBuilder.withSourceFilter(sourceFilter);
            queries.add(queryBuilder.build());
        }
        List<SearchHits<Product>> searchHits = elasticsearchRestTemplate.multiSearch(queries, Product.class);
        List<Product> products = new ArrayList<>(searchHits.size());
        searchHits.forEach(hits -> {
            if (hits.getSearchHits().size() > 0) {
                products.add(hits.getSearchHit(0).getContent());
            }
        });
        return products;
    }
}
